/* Lua.c - Lua plugin for TCC */
/* vim: set et: */

#define UNICODE 1
#define _UNICODE 1

#include <stdio.h>
#include <tchar.h>

#include <windows.h>

#include "PlugIn.h"
#include "TakeCmd.h"

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

/* ************************************************************************
 * Interpreter management
 * ************************************************************************/

static int init_interp(unsigned int n, BOOL reset);
static int tcc_open (lua_State *L);

static lua_State *interp[10] = {0,0,0,0,0,0,0,0,0,0};
static int current_interp = 0;
#define NUM_INTERP (sizeof(interp)/sizeof(*interp))

static int init_interp(unsigned int n, BOOL reset)
{
    int ok;

    if (n >= NUM_INTERP) {
        /* Interpreter number out of range */
        HANDLE err = GetStdHandle(STD_ERROR_HANDLE);
        Qprintf(err, L"Invalid Lua interpreter %d\n", n);
        return -1;
    }

    /* Does the interpreter already exist? */
    if (interp[n]) {
        /* Nothing to do unless we're being asked to reset it */
        if (!reset)
            return 0;

        /* Clean up the old interpreter */
        lua_close(interp[n]);
        interp[n] = 0;
    }

    /* Create the interpreter */
    interp[n] = luaL_newstate();
    ok = (interp[n] != NULL);

    /* Reoprt any errors in initialisation */
    if (!ok) {
        if (interp[n])
            lua_close(interp[n]);
        interp[n] = NULL;
        HANDLE err = GetStdHandle(STD_ERROR_HANDLE);
        Qprintf(err, L"Cannot initialize Lua interpreter %d\n", n);
        return -1;
    }

    /* Open the standard libraries */
    /* TODO - Cannot check this for errors!! */
    luaL_openlibs(interp[n]);

    /* Add the TCC interface library */
    /* TODO: lua_cpcall didn't work?! */
    tcc_open(interp[n]);

    return 0;
}

/* ************************************************************************
 * Conversion to and from Unicode
 * ************************************************************************/

static void push_tccstring(lua_State *L, const TCHAR *tstr)
{
    unsigned int size;
    char *out;
    size_t len = lstrlen(tstr);

    /* First, establish the size of the buffer needed */
    size = WideCharToMultiByte(CP_UTF8, 0, tstr, len, 0, 0, 0, 0);
    if (size == 0)
        luaL_error(L, "Cannot convert string to UTF-8");
    /* Now get memory and do the conversion */
    out = lua_newuserdata(L, size);
    size = WideCharToMultiByte(CP_UTF8, 0, tstr, len, out, size, 0, 0);
    if (size == 0)
        luaL_error(L, "Cannot convert string to UTF-8");
    /* Push the string and discard the memory block */
    lua_pushlstring(L, out, size);
    lua_remove(L, -2);
}

/* Note: This adds a value to the Lua stack */
static TCHAR *to_tccstring(lua_State *L, int index)
{
    unsigned int size;
    TCHAR *buf;
    size_t len;
    const char *str = lua_tolstring(L, index, &len);

    if (str == NULL)
        return NULL;

    /* First, establish the size of the buffer needed */
    size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, len, 0, 0);
    if (size == 0)
        luaL_error(L, "Cannot convert string from UTF-8");
    /* Allocate a suitable buffer and translate the string */
    buf = lua_newuserdata(L, (size + 1) * sizeof(TCHAR));
    size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
            str, len, buf, size);
    if (size == 0)
        luaL_error(L, "Cannot convert string from UTF-8");
    /* Null terminate */
    buf[size] = 0;
    return buf;
}

/* ************************************************************************
 * Utility functions
 * ************************************************************************/
/* Evaluate an expression */
void eval_expr (lua_State *L)
{
    const char *expr;
    const char *code;
    lua_pushliteral(L, "return (");
    lua_pushvalue(L, -2);
    lua_pushliteral(L, ")");
    lua_concat(L, 3);
    expr = lua_tostring(L, -2);
    code = lua_tostring(L, -1);
    if (luaL_dostring(L, code))
        luaL_error(L, "Invalid expression: '%s'", expr);
    lua_remove(L, -2);
}

/* ************************************************************************
 * Lua interface to TCC
 * ************************************************************************/

/* ************************************************************************
 * Execute a TCC internal command from Lua
 * ************************************************************************/
static int tcc_command (lua_State *L)
{
    const char *name = lua_tostring(L, lua_upvalueindex(1));
    TCHAR *cmd;
    HMODULE hTC = GetModuleHandle(L"TakeCmd");
    typedef int (*APIFN) (LPTSTR);
    APIFN api_fn;

    luaL_checkstring(L, 1);
    cmd = to_tccstring(L, 1);

    api_fn = (APIFN)GetProcAddress(hTC, name);
    api_fn(cmd);
    return 0;
}

struct APIFN_TABLE {
    const char *lua_name;
    const char *dll_name;
}
apifn_table[] = {
    {"activate", "Activate_Cmd"},
    {"alias", "Alias_Cmd"},
    {"assoc", "Assoc_Cmd"},
    {"attrib", "Attrib_Cmd"},
    {"bdebugger", "BDebugger_Cmd"},
    {"batcomp", "BatComp_Cmd"},
    {"battext", "Battext_Cmd"},
    {"beep", "Beep_Cmd"},
    {"break", "Break_Cmd"},
    {"breakpoint", "Breakpoint_Cmd"},
    {"call", "Call_Cmd"},
    {"cancel", "Cancel_Cmd"},
    {"case", "Case_Cmd"},
    {"cd", "Cd_Cmd"},
    {"cdd", "Cdd_Cmd"},
    {"chcp", "Chcp_Cmd"},
    {"cls", "Cls_Cmd"},
    {"cmds", "Cmds_Cmd"},
    {"color", "Color_Cmd"},
    {"copy", "Copy_Cmd"},
    {"date", "Date_Cmd"},
    {"debugstring", "DebugString_Cmd"},
    {"defer", "Defer_Cmd"},
    {"del", "Del_Cmd"},
    {"delay", "Delay_Cmd"},
    {"describe", "Describe_Cmd"},
    {"detach", "Detach_Cmd"},
    {"dirhistory", "DirHistory_Cmd"},
    {"dir", "Dir_Cmd"},
    {"dirs", "Dirs_Cmd"},
    {"do", "Do_Cmd"},
    {"drawhline", "DrawHline_Cmd"},
    {"drawvline", "DrawVline_Cmd"},
    {"drawbox", "Drawbox_Cmd"},
    {"echoerr", "EchoErr_Cmd"},
    {"echo", "Echo_Cmd"},
    {"echoserr", "EchosErr_Cmd"},
    {"echos", "Echos_Cmd"},
    {"ejectmedia", "EjectMedia_Cmd"},
    {"endlocal", "Endlocal_Cmd"},
    {"eset", "Eset_Cmd"},
    {"eventmonitor", "EventMonitor_Cmd"},
    {"eventlog", "Eventlog_Cmd"},
    {"except", "Except_Cmd"},
    {"exit", "Exit_Cmd"},
    {"ffind", "Ffind_Cmd"},
    {"firewiremonitor", "FirewireMonitor_Cmd"},
    {"foldermonitor", "FolderMonitor_Cmd"},
    {"for", "For_Cmd"},
    {"free", "Free_Cmd"},
    {"ftype", "Ftype_Cmd"},
    {"function", "Function_Cmd"},
    {"global", "Global_Cmd"},
    {"gosub", "Gosub_Cmd"},
    {"goto", "Goto_Cmd"},
    {"head", "Head_Cmd"},
    {"help", "Help_Cmd"},
    {"history", "History_Cmd"},
    {"iftp", "IFTP_Cmd"},
    {"if", "If_Cmd"},
    {"iff", "Iff_Cmd"},
    {"inkey", "Inkey_Cmd"},
    {"input", "Input_Cmd"},
    {"jabber", "Jabber_Cmd"},
    {"keybd", "Keybd_Cmd"},
    {"keys", "Keys_Cmd"},
    {"keystack", "Keystack_Cmd"},
    {"list", "List_Cmd"},
    {"loadmedia", "LoadMedia_Cmd"},
    {"loadbtm", "Loadbtm_Cmd"},
    {"log", "Log_Cmd"},
    {"md", "Md_Cmd"},
    {"memory", "Memory_Cmd"},
    {"mklnk", "Mklnk_Cmd"},
    {"msgbox", "Msgbox_Cmd"},
    {"mv", "Mv_Cmd"},
    {"netmonitor", "NetMonitor_Cmd"},
    {"osd", "OSD_Cmd"},
    {"on", "On_Cmd"},
    {"option", "Option_Cmd"},
    {"pdir", "PDir_Cmd"},
    {"path", "Path_Cmd"},
    {"pause", "Pause_Cmd"},
    {"playavi", "Playavi_Cmd"},
    {"playsound", "Playsound_Cmd"},
    {"plugin", "Plugin_Cmd"},
    {"popd", "Popd_Cmd"},
    {"postmsg", "PostMsg_Cmd"},
    {"print", "Print_Cmd"},
    {"priority", "Priority_Cmd"},
    {"processmonitor", "ProcessMonitor_Cmd"},
    {"prompt", "Prompt_Cmd"},
    {"pushd", "Pushd_Cmd"},
    {"querybox", "Querybox_Cmd"},
    {"quit", "Quit_Cmd"},
    {"rd", "Rd_Cmd"},
    {"reboot", "Reboot_Cmd"},
    {"recycle", "Recycle_Cmd"},
    {"remark", "Remark_Cmd"},
    {"ren", "Ren_Cmd"},
    {"ret", "Ret_Cmd"},
    {"rexec", "Rexec_Cmd"},
    {"rshell", "Rshell_Cmd"},
    {"smpp", "SMPP_Cmd"},
    {"snmp", "SNMP_Cmd"},
    {"snpp", "SNPP_Cmd"},
    {"scr", "Scr_Cmd"},
    {"script", "Script_Cmd"},
    {"scrput", "Scrput_Cmd"},
    {"select", "Select_Cmd"},
    {"sendmail", "SendMail_Cmd"},
    {"servicemonitor", "ServiceMonitor_Cmd"},
    {"services", "Services_Cmd"},
    {"set", "Set_Cmd"},
    {"setdos", "Setdos_Cmd"},
    {"setlocal", "Setlocal_Cmd"},
    {"shift", "Shift_Cmd"},
    {"shortcut", "Shortcut_Cmd"},
    {"shralias", "Shralias_Cmd"},
    {"start", "Start_Cmd"},
    {"switch", "Switch_Cmd"},
    {"sync", "Sync_Cmd"},
    {"tcfilter", "TCFilter_Cmd"},
    {"tctoolbar", "TCToolbar_Cmd"},
    {"tail", "Tail_Cmd"},
    {"taskdialog", "TaskDialog_Cmd"},
    {"taskend", "Taskend_Cmd"},
    {"tasklist", "Tasklist_Cmd"},
    {"tee", "Tee_Cmd"},
    {"time", "Time_Cmd"},
    {"timer", "Timer_Cmd"},
    {"title", "Title_Cmd"},
    {"touch", "Touch_Cmd"},
    {"transient", "Transient_Cmd"},
    {"tree", "Tree_Cmd"},
    {"truename", "Truename_Cmd"},
    {"type", "Type_Cmd"},
    {"usbmonitor", "USBMonitor_Cmd"},
    {"unalias", "Unalias_Cmd"},
    {"unfunction", "Unfunction_Cmd"},
    {"unset", "Unset_Cmd"},
    {"vscrput", "VScrput_Cmd"},
    {"ver", "Ver_Cmd"},
    {"verify", "Verify_Cmd"},
    {"volume", "Volume_Cmd"},
    {"wmiquery", "WMIQuery_Cmd"},
    {"which", "Which_Cmd"},
    {"window", "Window_Cmd"},
    {"y", "Y_Cmd"},
    {0, 0}
};

static int create_apifn_table (lua_State *L)
{
    struct APIFN_TABLE *a;
    lua_createtable(L, 0, sizeof(apifn_table)/sizeof(*apifn_table)-1);

    for (a = apifn_table; a->lua_name; ++a) {
        lua_pushstring(L, a->lua_name);
        lua_pushstring(L, a->dll_name);
        lua_pushcclosure(L, tcc_command, 1);
        lua_settable(L, -3);
    }
    return 1;
}

/* ************************************************************************
 * Execute a TCC command line from Lua
 * ************************************************************************/
static int tcc_exec (lua_State *L)
{
    LPTSTR cmd;
    luaL_checkstring(L, 1);
    cmd = to_tccstring(L, 1);
    Command(cmd, 0);
    return 0;
}

/* ************************************************************************
 * Environment metatable functions
 * ************************************************************************/
static int env_index (lua_State *L)
{
    LPTSTR var;
    LPTSTR val;
    int n;

    luaL_checkstring(L, 2);
    var = to_tccstring(L, 2);
    n = GetEnvironmentVariable(var, 0, 0);
    if (n == 0) {
        if (GetLastError() != ERROR_ENVVAR_NOT_FOUND)
            luaL_error(L, "Could not get environment variable");
        lua_pushnil(L);
        return 1;
    }
    val = (LPTSTR)lua_newuserdata(L, n);
    if (!GetEnvironmentVariable(var, val, n))
        luaL_error(L, "Could not get environment variable");

    push_tccstring(L, val);
    lua_remove(L, -2);
    return 1;
}

static int env_newindex (lua_State *L)
{
    LPTSTR var;
    LPTSTR val = NULL;

    luaL_checkstring(L, 2);
    var = to_tccstring(L, 2);
    if (!lua_isnil(L, 3)) {
        luaL_checkstring(L, 3);
        val = to_tccstring(L, 3);
    }
    SetEnvironmentVariable(var, val);
    return 0;
}

/* ************************************************************************
 * Internal implementation of print. Lifted from the Lua sources and
 * modified to use TakeCmd.dll functions for output
 * ************************************************************************/
static int c_print (lua_State *L) {
  int n = lua_gettop(L);  /* number of arguments */
  int i;
  lua_getglobal(L, "tostring");
  for (i=1; i<=n; i++) {
    LPTSTR s;
    lua_pushvalue(L, -1);  /* function to be called */
    lua_pushvalue(L, i);   /* value to print */
    lua_call(L, 1, 1);
    s = to_tccstring(L, -1);  /* get result */
    if (s == NULL)
      return luaL_error(L, LUA_QL("tostring") " must return a string to "
                           LUA_QL("print"));
    if (i>1) Printf(L"\t");
    Printf(L"%s", s);
    lua_pop(L, 1);  /* pop result */
  }
  Printf(L"\n");
  return 0;
}

/* ************************************************************************
 * Function registration tables
 * ************************************************************************/
static const struct luaL_reg libtcc[] = {
    { "exec", tcc_exec },
    { NULL, NULL }
};

static const struct luaL_reg envmeta[] = {
    { "__index", env_index },
    { "__newindex", env_newindex },
    { NULL, NULL }
};

/* ************************************************************************
 * Load the TCC interface into a Lua state
 * ************************************************************************/
static int tcc_open (lua_State *L)
{
    /* Register the library functions */
    luaL_register(L, "tcc", libtcc);

    /* The environment object stores no data itself, and so any object will do
     * as the base object. We will therefore use the same table as both the
     * metatable and the object. I don't know if this is good practice, or
     * unnecessarily tricky, but it saves an object and doesn't feel any more
     * complex than the alternative.
     */
    /* Create the table, register the metamethods, set it as its own
     * metatable, and assign it to tcc.env.
     */
    lua_newtable(L);
    luaL_register(L, NULL, envmeta);
    lua_pushvalue(L, -1);
    lua_setmetatable(L, -2);
    lua_pushstring(L, "env");   /* Now have tcc, env, "env" */
    lua_insert(L, -2);          /* Now have tcc, "env", env */
    lua_settable(L, -3);        /* tcc.env = env */

    /* Override the global print function */
    lua_pushstring(L, "print");
    lua_pushcfunction(L, c_print);
    lua_settable(L, LUA_GLOBALSINDEX);

    /* Get rid of the io.stdin, io.stdout and io.stderr objects */
    lua_getglobal(L, "io");
    lua_pushstring(L, "stdin");
    lua_pushnil(L);
    lua_settable(L, -3);
    lua_pushstring(L, "stdout");
    lua_pushnil(L);
    lua_settable(L, -3);
    lua_pushstring(L, "stderr");
    lua_pushnil(L);
    lua_settable(L, -3);
    lua_pop(L, 1);

    /* Create the table of API functions */
    lua_pushliteral(L, "api");
    create_apifn_table(L);
    lua_settable(L, -3);

    /* Return the library object */
    return 1;
}

/* ************************************************************************
 * Create an argument list from a TCC command line
 * ************************************************************************/

static int create_args (lua_State *L, LPTSTR str)
{
    int i;
    TCHAR buf[2048];

    for (i = 0; ; ++i) {
        TCHAR *nthptr;
        TCHAR *arg;
        arg = NthArgument(str, 0, buf, &nthptr);
        str = NextArgument(str, 1);
        if (!arg || !*arg) break;
        StripEnclosingQuotes(arg);
        push_tccstring(L, arg);
    }

    return i;
}

/* ************************************************************************
 * File readers
 * ************************************************************************/

static const char *ConsoleReader (lua_State *L, void *data, size_t *size)
{
    WCHAR buf[2048];

    Printf(L"Lua> ");
    GetLine(GetStdHandle(STD_INPUT_HANDLE), buf, 2048, 0x10000);

    if (buf[0] == L'.' && buf[1] == 0) {
        *size = 0;
        return NULL;
    }

    push_tccstring(L, buf);
    /* Preserve the string in the reserved stack slot (index 1) */
    lua_replace(L, 1);
    return lua_tolstring(L, 1, size);
}

#define READBUF 2048
static const char *StdinUnicodeReader (lua_State *L, void *data, size_t *size)
{
    HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
    DWORD n;
    WCHAR buf[READBUF+1];

    BOOL ok = ReadFile(in, buf, READBUF * sizeof(WCHAR), &n, 0);

    if (!ok || n == 0) {
        *size = 0;
        return NULL;
    }

    buf[n] = 0;
    push_tccstring(L, buf);
    /* Preserve the string in the reserved stack slot (index 1) */
    lua_replace(L, 1);
    return lua_tolstring(L, 1, size);
}

static const char *StdinAnsiReader (lua_State *L, void *data, size_t *size)
{
    HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
    DWORD n;
    char buf[READBUF+1];
    WCHAR wbuf[READBUF+1];
    UINT cp = (UINT)data;

    BOOL ok = ReadFile(in, buf, READBUF, &n, 0);

    if (!ok || n == 0) {
        *size = 0;
        return NULL;
    }

    /* We can never need more Unicode characters than ANSI ones */
    n = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buf, n, wbuf, READBUF);
    if (n == 0) {
        luaL_error(L, "Invalid cannot convert file to Unicode");
    }
    wbuf[n] = 0;
    push_tccstring(L, wbuf);
    /* Preserve the string in the reserved stack slot (index 1) */
    lua_replace(L, 1);
    return lua_tolstring(L, 1, size);
}

/* ************************************************************************
 * Error reporting
 * ************************************************************************/

static void report_error (lua_State *L, int status)
{
    if (status && !lua_isnil(L, -1)) {
        /* Use a static buffer and ASCII conversion here, as we may not
         * be in a position to allocate Lua memory.
         */
        HANDLE herr = GetStdHandle(STD_ERROR_HANDLE);
        TCHAR buf[1024];
        const char *msg = lua_tostring(L, -1);
        if (msg == NULL)
            msg = "(error object is not a string)";
        ASCIIToUnicode((char*)msg, buf, 1024);
        Qprintf(herr, L"Lua error: %s\n", buf);
        lua_pop(L, 1);
    }
}

/* ************************************************************************
 * Utility functions to run Lua code
 *
 * These are lua_CFunctions to be called via lua_cpcall
 * ************************************************************************/

static int pexec_chunk (lua_State *L)
{
    TCHAR *str = lua_touserdata(L, -1);
    const char *code;
    size_t len;

    push_tccstring(L, str);
    code = lua_tolstring(L, -1, &len);
    if (luaL_loadbuffer(L, code, len, "command line"))
        lua_error(L);
    lua_call(L, 0, 0);
    return 0;
}

static int pexec_fn (lua_State *L)
{
    TCHAR **fnarg = lua_touserdata(L, -1);
    TCHAR *fn = fnarg[0];
    TCHAR *args = fnarg[1];
    int nargs;
    push_tccstring(L, fn);
    lua_pushvalue(L, -1);
    eval_expr(L);
    if (!lua_isfunction(L, -1)) {
        luaL_error(L, "Invalid function %s", lua_tostring(L, -2));
    }
    nargs = create_args(L, args);
    lua_call(L, nargs, 0);
    return 0;
}

static int pexec_file (lua_State *L)
{
    TCHAR *file = lua_touserdata(L, -1);
    const char *name;
    push_tccstring(L, file);
    name = lua_tostring(L, -1);
    luaL_loadfile(L, name);
    lua_call(L, 0, 0);
    return 0;
}

static int pexec_stdin (lua_State *L)
{
    lua_Reader reader;
    HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
    UINT cp = CP_ACP;

    if (QueryIsConsole(in))
        return 0;

    if (GetFileType(in) == FILE_TYPE_PIPE) {
        reader = QueryUnicodeOutput() ? StdinUnicodeReader : StdinAnsiReader;
        cp = CP_OEMCP;
    } else {
        reader = QueryIsFileUnicode(in) ? StdinUnicodeReader : StdinAnsiReader;
    }

    /* Reserve a stack entry - number 1 - used in ConsoleReader */
    lua_pushnil(L);
    if (lua_load(L, reader, (void*)cp, "stdin"))
        lua_error(L);
    lua_call(L, 0, 0);
    return 0;
}

static int pexec_console (lua_State *L)
{
    if (!QueryIsConsole(GetStdHandle(STD_INPUT_HANDLE)))
        return 0;

    /* Reserve a stack entry - number 1 - used in ConsoleReader */
    lua_pushnil(L);
    if (lua_load(L, ConsoleReader, 0, "console"))
        lua_error(L);
    lua_call(L, 0, 0);
    return 0;
}

struct FnArgs {
    TCHAR *arg;
    TCHAR *result;
};

static int pcall_chunk (lua_State *L)
{
    struct FnArgs *args = lua_touserdata(L, -1);
    TCHAR *str = args->arg;
    const TCHAR *result;

    push_tccstring(L, str);
    eval_expr(L);
    result = to_tccstring(L, -1);
    if (result == NULL)
        luaL_error(L, "Return value is not a string");
    lstrcpy(args->result, result);
    return 0;
}

static int pcall_fn (lua_State *L)
{
    struct FnArgs *args = lua_touserdata(L, -1);
    TCHAR *str = args->arg;
    TCHAR *nthptr;
    TCHAR buf[2048];
    LPTSTR fn = NthArgument(str, 0, buf, &nthptr);
    int nargs;
    const TCHAR *result;

    StripEnclosingQuotes(fn);
    str = NextArgument(str, 1);
    push_tccstring(L, fn);
    lua_pushvalue(L, -1);
    eval_expr(L);
    if (!lua_isfunction(L, -1)) {
        luaL_error(L, "Invalid function %s", lua_tostring(L, -2));
    }
    nargs = create_args(L, str);
    lua_call(L, nargs, 1);
    result = to_tccstring(L, -1);
    if (result == NULL)
        luaL_error(L, "Return value is not a string");
    lstrcpy(args->result, result);
    return 0;
}

/* ************************************************************************
 * Exposed plugin commands
 * ************************************************************************/

/* LUA chunk
 *
 * Execute a chunk of code with the default Lua interpreter. If there is no
 * chunk supplied, read a chunk from standard input - this allows the use of
 * redirection syntax like
 *
 *     LUA <<EOF
 *     chunk
 *     EOF
 *
 * Command formats:
 *
 * LUA [/0.../9] [/C] /F fn arg...        Execute function
 * LUA [/0.../9] [/C] /R file             Read file
 * LUA [/0.../9] [/C] /I                  Read console (interactive)
 * LUA [/0.../9] [/C] chunk               Execute chunk
 * LUA [/0.../9] [/C]                     Read redirected standard input
 *
 * /0.../9 specifies the interpreter to use (0 is the default)
 * /C clears the interpreter before executing the code
 */
DLLExports INT WINAPI lua (LPTSTR lpszString)
{
    TCHAR buf[2048];
    int i;
    int num = current_interp;
    BOOL reset = FALSE;
    BOOL interactive = FALSE;
    BOOL args_done = FALSE;
    TCHAR *fn = NULL;
    TCHAR *file = NULL;
    lua_State *L;
    int status;

    for (i = 0; !args_done; ++i) {
        TCHAR *nthptr;
        LPTSTR arg = NthArgument(lpszString, i, buf, &nthptr);
        if (!arg || !*arg || *arg != L'/')
            break;
        if (arg[2] != 0) {
            // Error invalid arg
            return 1;
        }
        switch (arg[1]) {
            case L'0': case L'1': case L'2': case L'3': case L'4':
            case L'5': case L'6': case L'7': case L'8': case L'9':
                num = arg[1] - L'0';
                break;
            case L'C': case L'c':
                reset = TRUE;
                break;
            case L'F': case L'f':
                ++i;
                fn = NthArgument(lpszString, i, buf, &nthptr);
                StripEnclosingQuotes(fn);
                args_done = TRUE;
                break;
            case L'R': case L'r':
                ++i;
                file = NthArgument(lpszString, i, buf, &nthptr);
                StripEnclosingQuotes(file);
                args_done = TRUE;
                break;
            case L'I': case L'i':
                interactive = TRUE;
                args_done = TRUE;
                break;
            default:
                args_done = TRUE;
                break;
        }
    }
    lpszString = NextArgument(lpszString, i);
    if ((file || interactive) && lpszString && *lpszString) {
        // Error junk at end
        return 1;
    }


    /* Create or reset the interpreter as necessary */
    if (init_interp(num, reset) == -1)
        return -1;
    L = interp[num];

    if (fn) {
        TCHAR *fnargs[] = { fn, lpszString };
        status = lua_cpcall(L, pexec_fn, fnargs);
    }
    else if (file)
        status = lua_cpcall(L, pexec_file, file);
    else if (interactive)
        status = lua_cpcall(L, pexec_console, 0);
    else if (lpszString && *lpszString)
        status = lua_cpcall(L, pexec_chunk, lpszString);
    else
        status = lua_cpcall(L, pexec_stdin, 0);

    report_error(L, status);
    return status ? -1 : 0;
}


/* ************************************************************************
 * Exposed plugin functions
 * ************************************************************************/

DLLExports INT WINAPI f_lua (LPTSTR lpszString)
{
    struct FnArgs args;
    int status;
    LPTSTR str = lpszString;
    lua_State *L;

    /* Check we have an argument */
    if (str == NULL)
        return 1;
    str = NextArgument(str, 0);
    if (!*str)
        return 1;

    /* Get the interpreter */
    if (init_interp(current_interp, FALSE) == -1)
        return 1;
    L = interp[current_interp];

    /* Run the guts of this function in Lua protected mode */
    args.result = lpszString;
    args.arg = str;
    if (*str == L'=') {
        ++args.arg;
        status = lua_cpcall(L, pcall_chunk, &args);
    }
    else {
        status = lua_cpcall(L, pcall_fn, &args);
    }
    report_error(L, status);
    return status ? -1 : 0;
}

/* ************************************************************************
 * Exposed plugin variables
 * ************************************************************************/

/* %_LUA_VERSION - the version of Lua which is loaded */
DLLExports INT WINAPI _lua_version (LPTSTR lpszString)
{
    /* Should never happen, but check anyway */
    if (lpszString == NULL)
        return 1;

    /* Supplied string buffer is documented as 2048 characters (plugin.h) */
    ASCIIToUnicode(LUA_VERSION, lpszString, 2048);
    return 0;
}

/* %_LUA_RELEASE - the release (e.g. 5.1.3) of Lua which is loaded */
DLLExports INT WINAPI _lua_release (LPTSTR lpszString)
{
    /* Should never happen, but check anyway */
    if (lpszString == NULL)
        return 1;

    /* Supplied string buffer is documented as 2048 characters (plugin.h) */
    ASCIIToUnicode(LUA_RELEASE, lpszString, 2048);
    return 0;
}

/* ************************************************************************
 * DLL management function
 * ************************************************************************/

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  dwReason, LPVOID lpReserved )
{
    switch( dwReason )  {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

/* ************************************************************************
 * Plugin interface - exported interface functions
 * ************************************************************************/

DLLExports LPPLUGININFO WINAPI GetPluginInfo( void )
{
    static PLUGININFO piInfo;

    piInfo.pszDll = (LPTSTR)L"Lua";
    piInfo.pszAuthor = (LPTSTR)L"Paul Moore";
    piInfo.pszEmail = (LPTSTR)L"pf_moore@yahoo.co.uk";
    piInfo.pszWWW = (LPTSTR)L"http://www.arkenstone.demon.co.uk";
    piInfo.pszDescription = (LPTSTR)L"Lua language interface";
    piInfo.pszFunctions = (LPTSTR)L"@lua,_lua_version,_lua_release,lua";
    piInfo.nMajor = 1;
    piInfo.nMinor = 0;
    piInfo.nBuild = 1;

    return &piInfo;
}

DLLExports BOOL WINAPI InitializePlugin( void )
{
    init_interp(current_interp, TRUE);
    return 0;
}


DLLExports BOOL WINAPI ShutdownPlugin( BOOL bEndProcess )
{
    int i;
    for (i = 0; i < NUM_INTERP; ++i) {
        if (interp[i]) {
            lua_close(interp[i]);
            interp[i] = 0;
        }
    }
    return 0;
}

